#include "Material.h"

#include "SceneManagerEnumerator.h"
#include "MaterialManager.h"
#include "IteratorWrappers.h"
#include "Technique.h"
#include "LogManager.h"
#include "Exception.h"
#include "StringConverter.h"
#include "LodStrategy.h"
#include "LodStrategyManager.h"

namespace PVM
{
	Material::Material(ResourceManager* creator, const std::string& name, ResourceHandle handle,
					   const std::string& group, bool isManual, ManualResourceLoader* loader)
	: Resource(creator, name, handle, group, isManual, loader),
	mReceiveShadows(true),
	mTransparencyCastsShadows(false),
	mCompilationRequired(true)
	{
		if (isManual)
		{
			mIsManual = false;
			LogManager::getSingleton().logMessage("Material " + name + 
												  " was requested with isManual = true, but this is not applicable "
												  "for materials; the flag has been reset to false");
		}
		
		mLodStrategy = LodStrategyManager::getSingleton().getDefaultStrategy();
		mLodValues.push_back(0.0f);
		applyDefaults();
		createParamDictionary("Material");
	}
	
	Material::~Material()
	{
		removeAllTechniques();
		unload();
	}
	
	Material& Material::operator=(const Material& rhs)
	{
		mName = rhs.mName;
		mGroup = rhs.mGroup;
		mCreator = rhs.mCreator;
		mIsManual = rhs.mIsManual;
		mLoader = rhs.mLoader;
		mHandle = rhs.mHandle;
		mSize = rhs.mSize;
		mReceiveShadows = rhs.mReceiveShadows;
		mTransparencyCastsShadows = rhs.mTransparencyCastsShadows;
		
		mLoadingState = rhs.mLoadingState;
		mIsBackgroundLoaded = rhs.mIsBackgroundLoaded;
		
		this->removeAllTechniques();
		Techiniques::const_iterator i, iend;
		iend = rhs.mTechniques.end();
		for (i = rhs.mTechniques.begin(); i != iend; ++i)
		{
			Techqniue* t = this->createTechnique();
			*t = *(*i);
			if ((*i)->isSupported())
			{
				insertSupportedTechnique(t);
			}
		}
		
		mUserLodValues = rhs.mUserLodValues;
		mLodValues = rhs.mLodValues;
		mLodStrategy = rhs.mLodStrategy;
		mCompilationRequired = rhs.mCompilationRequired;
		assert(isLoaded() == rhs.isLoaded());
		
		return *this;
	}
	
	void Material::prepareImpl(void)
	{
		if (mCompilationRequired)
			compile();
		
		Technique::iterator i, iend;
		iend = mSupportedTechniques.end();
		for (i = mSupportedTechniques.begin(); i != iend; ++i)
		{
			(*i)->_prepare();
		}
	}
	
	void Material::unprepareImpl(void)
	{
		Techniques::iterator i, iend;
		iend = mSupportedTechniques.end();
		for (i = mSupportedTechniques.begin(); i != iend; ++i)
		{
			(*i)->_unprepare();
		}
	}
	
	void Material::loadImpl(void)
	{
		Techniques::iterator i, iend;
		iend = mSupportedTechniques.end();
		for (i = mSupportedTechniques.begin(); i != iend; ++i)
		{
			(*i)->_load();
		}
	}
	
	void Material::unloadImpl(void)
	{
		Techniques::iterator i, iend;
		iend = mSupportedTechniques.end();
		for (i = mSupportedTechniques.begin(); i != iend; ++i)
		{
			(*i)->_unload();
		}
	}
	
	MaterialPtr Material::clone(const std::string& newName, bool changeGroup,
								const std::string& newGroup) const
	{
		MaterialPtr newMat;
		if (changeGroup)
		{
			newMat = MaterialManager::getSingleton().create(newName, newGroup);
		}
		else
		{
			newMat = MaterialManager::getSingleton().create(newName, mGroup);
		}

		ResourceHandle newHandle = newMat->getHandle();
		*newMat = *this;
		if (changeGroup)
		{
			newMat->mGroup = newGroup;
		}
		
		newMat->mName = newName;
		newMat->mHandle = newHandle;
		
		return newMat;
	}
	
	void Material::copyDetailsTo(MaterialPtr& mat) const
	{
		ResourceHandle savedHandle = mat->mHandle;
		std::string savedName = mat->mName;
		std::string savedGroup = mat->mGroup;
		ManualResourceLoader* savedLoader = mat->mLoader;
		bool savedManual = mat->mIsManual;
		
		*mat = *this;
		mat->mName = savedName;
		mat->mHandle = savedHandle;
		mat->mGroup = savedGroup;
		mat->mIsManual = savedManual;
		mat->mLoader = savedLoader;
	}
	
	void Material::applyDefaults(void)
	{
		MaterialPtr defaults = MaterialManager::getSingleton().getDefaultSettings();
		
		if (!defaults.isNull())
		{
			std::string savedName = mName;
			std::string savedGroup = mGroup;
			ResourceHandle savedHandle = mHandle;
			ManualResourceLoader* savedLoader = mLoader;
			bool savedManual = mIsManual;
			*this = *defaults;
			
			mName = savedName;
			mHandle = savedHandle;
			mGroup = savedGroup;
			mLoader = savedLoader;
			mIsManual = savedManual;
		}
		mCompilationRequired = true;
	}
	
	Technique* Material::createTechnique(void)
	{
		Technique* t = new Technique(this);
		mTechniques.push_back(t);
		mCompilationRequired = true;
		return t;
	}
	
	Technique* Material::getTechnique(unsigned short index)
	{
		assert(index < mTechniques.size() && "Index out of bounds.");
		return mTechniques[index];
	}
	
	Technique* Material::getTechnique(const std::string& name)
	{
		Techniques::iterator i = mTechniques.begin();
		Techniques::iterator iend = mTechniques.end();
		Technique* foundTechnique = 0;
		while (i != iend)
		{
			if ((*i)->getName() == name)
			{
				foundTechnique = (*i);
				break;
			}
			++i;
		}
		
		return foundTechnique;
	}
	
	unsigned short Material::getNumTechniques(void) const
	{
		return static_cast<unsigned short>(mTechniques.size());
	}
	
	Technique* Material::getSupportedTechnique(unsigned short index)
	{
		assert(index < mSupportedTechniques.size() && "Index out of bounds.");
		return mSupportedTechniques[index];
	}
	
	unsigned short Material::getNumSupportedTechniques(void) const
	{
		return static_cast<unsigned short>(mSupportedTechniques.size());
	}
	
	unsigned short Material::getNumLodLevels(unsigned short schemeIndex) const
	{
		if (mBestTechniquesBySchemeList.empty())
			return 0;
		
		BestTechniquesBySchemeList::const_iterator i = 
		mBestTechniquesBySchemeList.find(schemeIndex);
		if (i == mBestTechniquesBySchemeList.end())
		{
			i = mBestTechniquesBySchemeList.begin();
		}
		return static_cast<unsigned short>(i->second->size());
	}
	
	unsigned short Material::getNumLodLevels(const std::string& schemeName) const
	{
		return getNumLodLevels(
							   MaterialManager::getSingleton()._getSchemeIndex(schemeName));
	}
	
	void Material::insertSupportedTechnique(Technique* t)
	{
		mSupportedTechniques.push_back(t);
		
		unsigned short schemeIndex = t->_getSchemeIndex();
		BestTechniquesBySchemeList::iterator i = 
		mBestTechniquesBySchemeList.find(schemeIndex);
		LodTechniques* lodtechs = 0;
		if (i == mBestTechniquesBySchemeList.end())
		{
			lodtechs = new LodTechniques;
			mBestTechniquesBySchemeList[schemeIndex] = lodtechs;
		}
		else
		{
			lodtechs = i->second;
		}
		
		lodtechs->insert(LodTechniques::value_type(t->getLodIndex(), t));
	}
	
	Technique* Material::getBestTechnique(unsigned short lodIndex, const Renderable* rend)
	{
		if (mSupportedTechniques.empty())
		{
			return NULL;
		}
		else
		{
			Technique* ret = 0;
			MaterialManager& matMgr = MaterialManager::getSingleton();
			
			BestTechniquesBySchemeList::iterator si = 
			mBestTechniquesBySchemeList.find(matMgr._getActiveSchemeIndex());
			if (si == mBestTechniquesBySchemeList.end())
			{
				ret = matMgr._arbitrateMissingTechniqueForActiveScheme(this, lodIndex, rend);
				if (ret)
					return ret;
				
				si = mBestTechniquesBySchemeList.begin();
			}
			
			LodTechniques::iterator li = si->second->find(lodIndex);
			if (li == si->second->end())
			{
				for (LodTechniques::reverse_iterator rli = si->second->rbegin();
					 rli != si->second->rend(); ++rli)
				{
					if (rli->second->getLodIndex() < lodIndex)
					{
						ret = rli->second;
						break;
					}
				}
				if (!ret)
				{
					ret = si->second->begin()->second;
				}
			}
			else
			{
				ret = li->second;
			}
			
			return ret;
		}
	}
	
	void Material::removeTechnique(unsigned short index)
	{
		assert(index < mTechniques.size() && "Index out of bounds.");
		Techniques::iterator i = mTechniques.begin() + index;
		delete (*i):
		mTechniques.erase(i);
		mSupportedTechniques.clear();
		clearBestTechniqueList();
		mCompilationRequired = true;
	}
	
	void Material::removeAllTechniques(void)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			delete (*i);
		}
		mTechniques.clear();
		mSupportedTechniques.clear();
		clearBestTechniqueList();
		mCompilationRequired = true;
	}
	
	Material::TechniqueIterator Material::getTechniqueIterator(void)
	{
		return TechniqueIterator(mTechniques.begin(), mTechniques.end());
	}
	
	Material::TechniqueIterator Material::getSupportedTechniqueIterator(void)
	{
		return TechniqueIterator(mSupportedTechniques.begin(), mSupportedTechniques.end());
	}
	
	bool Material::isTransparent(void) const
	{
		Techniques::const_iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			if ((*i)->isTransparent())
				return true;
		}
		return false;
	}
	
	void Material::compile(bool autoManageTextureUnits)
	{
		mSupportedTechniques.clear();
		clearBestTechniqueList();
		mUnsupportedReasons.clear();
		
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		size_t techNo = 0;
		for (i = mTechniques.begin(); i != iend; ++i, ++techNo)
		{
			std::string compileMessages = (*i)->_compile(autoManageTextureUnits);
			if ((*i)->isSupported())
			{
				insertSupportedTechnique(*i);
			}
			else
			{
				StringUtil::StrStreamType str;
				str << "Material " << mName << " Technique " << techNo;
				if (!(*i)->getName().empty())
					str << "(" << (*i)->getName() << ")";
				str << " is not supported. " << compileMessages;
				LogManager::getSingleton().logMessage(str.str(), LML_TRIVIAL);
				mUnsupportedReasons += compileMessages;
			}
		}
		
		mCompilationRequired = false;
		
		if (mSupportedTechniques.empty())
		{
			LogManager::getSingleton().stream()
			<< "WARNING: material " << mName << " has no supportable "
			<< "Techniques and will be blank. Explanation: \n" << mUnsupportedReasons;
		}
	}
	
	void Material::clearBestTechniqueList(void)
	{
		for (BestTechniquesBySchemeList::iterator i = mBestTechniquesBySchemeList.begin();
			 i != mBestTechniquesBySchemeList.end(); ++i)
		{
			delete i->second;
		}
		mBestTechniquesBySchemeList.clear();
	}
	
	void Material::setPointSize(float ps)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setPointSize(ps);
		}
	}
	
	void Material::setAmbient(float red, float green, float blue)
	{
		setAmbient(ColourValue(red, green, blue));
	}
	
	void Material::setAmbient(const ColourValue& ambient)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setAmbient(ambient);
		}
	}
	
	void Material::setDiffuse(float red, float green, float blue, float alpha)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setDiffuse(red, green, blue, alpha);
		}
	}
	
	void Material::setDiffuse(const ColourValue& diffuse)
	{
		setDiffuse(diffuse.r, diffuse.g, diffuse.b, diffuse.a);
	}
	
	void Material::setSpecular(float red, float green, float blue, float alpha)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setSpecular(red, green, blue, alpha);
		}
	}
	
	void Material::setSpecular(const ColourValue& specular)
	{
		setSpecular(specular.r, specular.g, specular.b, specular.a);
	}
	
	void Material::setShininess(float val)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setShininess(val);
		}
	}
	
	void Material::setSelfIllumination(float red, float green, float blue)
	{
		setSelfIllumination(ColourValue(red, green, blue));
	}
	
	void Material::setSelfIllumination(const ColourValue& selfIllum)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setSelfIllumination(selfIllum);
		}
	}
	
	void Material::setDepthCheckEnabled(bool enabled)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setDepthCheckEnabled(enabled);
		}
	}
	
	void Material::setDepthWriteEnabled(bool enabled)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setDepthWriteEnabled(enabled);
		}
	}
	
	void Material::setDepthFunction(CompareFunction func)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setDepthFunction(func);
		}
	}
	
	void Material::setColourWriteEnabled(bool enabled)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setColourWriteEnabled(enabled);
		}
	}
	
	void Material::setCullingMode(CullingMode mode)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setCullingMode(mode);
		}
	}
	
	void Material::setManualCullingMode(ManualCullingMode mode)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setManualCullingMode(mode);
		}
	}
	
	void Material::setLightingEnabled(bool enabled)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setLightingEnabled(enabled);
		}
	}
	
	void Material::setShadingMode(ShadeOptions mode)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setShadingMode(mode);
		}
	}
	
	void Material::setFog(bool overrideScene, FogMode mode, const ColourValue& colour,
						  float expDensity, float linearStart, float linearEnd)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setFog(overrideScene, mode, colour, expDensity, linearStart, linearEnd);
		}
	}
	
	void Material::setDepthBias(float constantBias, float slopeScaleBias)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setDepthBias(constantBias, slopeScaleBias);
		}
	}
	
	void Material::setTextureFiltering(TextureFilteringOptions filterType)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setTextureFiltering(filterType);
		}
	}
	
	void Material::setTextureAnisotropy(int maxAniso)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setTextureAnisotropy(maxAniso);
		}
	}
	
	void Material::setSceneBlending(const SceneBlendType sbt)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setSceneBlending(sbt);
		}
	}
	
	void Material::setSeparateSceneBlending(const SceneBlendType sbt, const SceneBlendType sbta)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setSeparateSceneBlending(sbt, sbta);
		}
	}
	
	void Material::setSceneBlending(const SceneBlendFactor sourceFactor,
									const SceneBlendFactor destFactor)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setSceneBlending(sourceFactor, destFactor);
		}
	}
	
	void Material::setSeparateSceneBlending(const SceneBlendFactor sourceFactor, const SceneBlendFactor destFactor, const SceneBlendFactor sourceFactorAlpha, const SceneBlendFactor destFactorAlpha)
	{
		Techniques::iterator i, iend;
		iend = mTechniques.end();
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			(*i)->setSeparateSceneBlending(sourceFactor, destFactor, sourceFactorAlpha, destFactorAlpha);
		}
	}
	
	void Material::_notifyNeedsRecompile(void)
	{
		mCompilationRequired = true;
		
		if (isLoaded())
			unload();
	}
	
	void Material::setLodLevels(const LodValueList& lodValues)
	{
		LodValueList::const_iterator i, iend;
		iend = lodValues.end();
		
		mLodValues.clear();
		mUserLodValues.push_back(0);
		mLodValues.push_back(mLodStrategy->getBaseValue());
		for (i = lodValues.begin(); i != iend; ++i)
		{
			mUserLodValues.push_back(*i);
			if (mLodStrategy)
				mLodValues.push_back(mLodStrategy->transformUserValue(*i));
		}
	}
	
	ushort Material::getLodIndex(float value) const
	{
		return mLodStrategy->getIndex(value, mLodValues);
	}
	
	Material::LodValueIterator Material::getLodValueIterator(void) const
	{
		return LodValueIterator(mLodValues.begin(), mLodValues.end());
	}
	
	Material::LodValueIterator Material::getUserLodValueIterator(void) const
	{
		return LodValueIterator(mUserLodValues.begin(), mUserLodValues.end());
	}
	
	bool Material::applyTextureAliases(const AliasTextureNamePairList& aliasList, const bool apply) const
	{
		Techniques::const_iterator i, iend;
		iend = mTechniques.end();
		bool testResult = false;
		
		for (i = mTechniques.begin(); i != iend; ++i)
		{
			if ((*i)->applyTextureAliases(aliasList, apply))
				testResult = true;
		}
		
		return testResult;
	}
	
	const LodStrategy* Material::getLodStrategy() const
	{
		return mLodStrategy;
	}
	
	void Material::setLodStrategy(LodStrategy* lodStrategy)
	{
		mLodStrategy = lodStrategy;
		
		assert(mLodValues.size());
		mLodValues[0] = mLodStrategy->getBaseValue();
		
		for (size_t i = 1; i < mUserLodValues.size(); ++i)
			mLodValues[i] = mLodStrategy->transformUserValue(mUserLodValues[i]);
	}
}
